#include "interrupt.h"

#include "global.h"
#include "io.h"
#include "print.h"
#include "stdint.h"

extern uint32_t syscall_handler();

static struct gate_desc idt[IDT_DESC_CNT];

static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr,
                          intr_handler function);
// init programmable interrupt controller 8259A
static void pic_init();
static void exception_init(void);

char*        intr_name[IDT_DESC_CNT]; // 用于保存异常的名字
intr_handler idt_table
    [IDT_DESC_CNT]; // 定义中断处理程序数组.在kernel.S中定义的intrXXentry只是中断处理程序的入口,最终调用的是ide_table中的处理程序
extern intr_handler interrupt_entry_table[IDT_DESC_CNT];

// eflags 寄存器 if 位为1，打开中断
#define EFLAGS_IF 0x00000200
// 获取 eflags 寄存器
#define GET_EFLAGS(EFLAG_VAR) asm volatile("pushfl; popl %0" : "=g"(EFLAG_VAR))

void idt_init()
{
  put_str("idt_init start\r\n");
  idt_desc_init();
  exception_init();
  pic_init();

  // loading idt
  uint64_t idt_operand = ((sizeof(idt) - 1) | ((uint64_t)idt << 16));
  asm volatile("lidt %0" ::"m"(idt_operand));
  put_str("idt_init done\r\n");
}

void idt_desc_init()
{
  for (int i = 0; i < IDT_DESC_CNT; i++)
  {
    make_idt_desc(&idt[i], IDT_DESC_ATTR_DPL0, interrupt_entry_table[i]);
  }

  // 0x80 system call interrupt
  make_idt_desc(&idt[0x80], IDT_DESC_ATTR_DPL3, syscall_handler);

  put_str("  idt_desc_init done\r\n");
}

static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr,
                          intr_handler function)
{
  p_gdesc->func_offset_low_word = (uint32_t)function & 0x0000FFFF;
  p_gdesc->selector = SELECTOR_K_CODE;
  p_gdesc->dcount = 0;
  p_gdesc->attribute = attr;
  p_gdesc->func_offset_high_word = ((uint32_t)function & 0xFFFF0000) >> 16;
}

/* 初始化可编程中断控制器8259A */
static void pic_init()
{
  /* 初始化主片 */
  outb(PIC_M_CTRL, 0x11); // ICW1: 边沿触发,级联8259, 需要ICW4.
  outb(PIC_M_DATA,
       0x20); // ICW2: 起始中断向量号为0x20,也就是IR[0-7] 为 0x20 ~ 0x27.
  outb(PIC_M_DATA, 0x04); // ICW3: IR2接从片.
  outb(PIC_M_DATA, 0x01); // ICW4: 8086模式, 正常EOI

  /* 初始化从片 */
  outb(PIC_S_CTRL, 0x11); // ICW1: 边沿触发,级联8259, 需要ICW4.
  outb(PIC_S_DATA,
       0x28); // ICW2: 起始中断向量号为0x28,也就是IR[8-15] 为 0x28 ~ 0x2F.
  outb(PIC_S_DATA, 0x02); // ICW3: 设置从片连接到主片的IR2引脚
  outb(PIC_S_DATA, 0x01); // ICW4: 8086模式, 正常EOI

  /* 打开主片上IR0,也就是目前只接受时钟产生的中断 */
  outb(PIC_M_DATA, 0xfe);
  outb(PIC_S_DATA, 0xff);

  // 只打开键盘中断
  // outb(PIC_M_DATA, 0xfd);
  // outb(PIC_S_DATA, 0xff);

  put_str("  pic_init done\r\n");
}
/* 通用的中断处理函数,一般用在异常出现时的处理 */
static void general_intr_handler(uint8_t vec_nr)
{
  if (vec_nr == 0x27 || vec_nr == 0x2f)
  {         // 0x2f是从片8259A上的最后一个irq引脚，保留
    return; // IRQ7和IRQ15会产生伪中断(spurious interrupt),无须处理。
  }

  // 输出中断信息
  int cursor_pos = 0;
  // 屏幕左上角清理出一块空白区域，用于打印异常信息
  set_cursor(cursor_pos);
  while (cursor_pos < 320)
  {
    put_char(' ');
    cursor_pos++;
  }

  // 输出信息
  set_cursor(0);
  put_str("!!!!!!!!!!     Exception Message Begin     !!!!!!!!!!\r\n");
  set_cursor(88);
  // 输出中断信息
  put_str(intr_name[vec_nr]), put_str(": 0x"), put_int(vec_nr), put_str("\r\n");
  // 14号中断是缺页中断
  if (vec_nr == 14)
  {
    int page_fault_vaddr = 0;
    // 缺页中断发生后，CPU会将缺页的地址放到CR2上
    asm volatile("movl %%cr2, %0" : "=r"(page_fault_vaddr));
    put_str(
        "Page Fault happened because you tried to access an invalid addr: 0x");
    put_int(page_fault_vaddr);
  }
  else
  {
  }
  put_str("\r\n");
  put_str("!!!!!!!!!!     Exception Message End     !!!!!!!!!!\r\n");
  // 中断发生后，CPU自动关闭中断，所以此后程序不会再继续运行
  while (1)
    ;
}

/* 完成一般中断处理函数注册及异常名称注册 */
static void exception_init(void)
{ // 完成一般中断处理函数注册及异常名称注册
  int i;
  for (i = 0; i < IDT_DESC_CNT; i++)
  {
    /* idt_table数组中的函数是在进入中断后根据中断向量号调用的,
     * 见kernel/kernel.S的call [idt_table + %1*4] */
    idt_table[i] =
        general_intr_handler; // 默认为general_intr_handler。
                              // 以后会由register_handler来注册具体处理函数。
    intr_name[i] = "unknown"; // 先统一赋值为unknown
  }
  intr_name[0] = "#DE Divide Error";
  intr_name[1] = "#DB Debug Exception";
  intr_name[2] = "NMI Interrupt";
  intr_name[3] = "#BP Breakpoint Exception";
  intr_name[4] = "#OF Overflow Exception";
  intr_name[5] = "#BR BOUND Range Exceeded Exception";
  intr_name[6] = "#UD Invalid Opcode Exception";
  intr_name[7] = "#NM Device Not Available Exception";
  intr_name[8] = "#DF Double Fault Exception";
  intr_name[9] = "Coprocessor Segment Overrun";
  intr_name[10] = "#TS Invalid TSS Exception";
  intr_name[11] = "#NP Segment Not Present";
  intr_name[12] = "#SS Stack Fault Exception";
  intr_name[13] = "#GP General Protection Exception";
  intr_name[14] = "#PF Page-Fault Exception";
  // intr_name[15] 第15项是intel保留项，未使用
  intr_name[16] = "#MF x87 FPU Floating-Point Error";
  intr_name[17] = "#AC Alignment Check Exception";
  intr_name[18] = "#MC Machine-Check Exception";
  intr_name[19] = "#XM SIMD Floating-Point Exception";
}

enum intr_status intr_get_status()
{
  uint32_t eflags = 0;
  GET_EFLAGS(eflags);
  // 按位与 if 位为 1则中断被屏蔽
  return (EFLAGS_IF & eflags) ? INTR_ON : INTR_OFF;
}

enum intr_status intr_set_status(enum intr_status status)
{
  return status ? intr_enable() : intr_disable();
}

enum intr_status intr_enable()
{
  enum intr_status old_status;
  if (intr_get_status() == INTR_ON)
  {
    old_status = INTR_ON;
  }
  else
  {
    old_status = INTR_OFF;
    // 开中断，sti 指令将 if 置为 1
    asm volatile("sti");
  }

  return old_status;
}

enum intr_status intr_disable()
{
  enum intr_status old_status;
  if (intr_get_status() == INTR_ON)
  {
    old_status = INTR_ON;
    // 关中断，cli 指令将 if 置为 0
    asm volatile("cli" ::: "memory");
  }
  else
  {
    old_status = INTR_OFF;
  }

  return old_status;
}

void register_handler(uint8_t vector_no, intr_handler function)
{
  // see call [idt_table + %1+4] in lib/kernel/kernel.S
  idt_table[vector_no] = function;
}
